#!/bin/python
"""
Leyden Manager
A program to configure, update and analyse the Leyden charge controller
"""
# Copyright (C) 2018 Oliver Galvin
#
# Leyden is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Leyden is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Leyden.  If not, see <http://www.gnu.org/licenses/>.

from requests import get
import serial

REPO = "https://notabug.org/odg/leyden/"
BUGS = "Report bugs to <odg@riseup.net> or go to <" + REPO + ">."
MYPGP = "491E 0D9E E7AA 9E15 D089 950A 7879 6625 7046 CC21"
VERB = False

def verb(msg):
	"""Output only when in verbose mode"""
	if VERB:
		print(msg)

def getcoords():
	"""Get latitude and longitude from IP if possible, or from configuration file"""
	import GeoIP
	from math import radians
	ip = get('https://icanhazip.com').text
	geo = GeoIP.open("/usr/share/GeoIP/GeoIPCity.dat", GeoIP.GEOIP_STANDARD).record_by_name(ip)
	lat = radians(geo['latitude'])
	lng = radians(geo['longitude'])
	return lat, lng

def gensuntimes():
	"""Generates a list of sunset and sunrise times"""
	from datetime import datetime
	from math import sin, cos, asin, acos, radians, floor, pi
	lat, lng = getcoords()
	today = floor((datetime.now().timestamp() - 946684800) / 86400) + (68.184 / 86400)
	n = today
	verb("Calculating sunrise/sunset times...")
	with open("sun", 'w') as f:
		while n <= today + (365 * 100):
			J = n - lng / (2 * pi)
			M = radians(357.5291 + (0.98560028 * J)) % (2 * pi)
			C = (1.9148 * sin(M)) + (0.02 * sin(2 * M)) + (0.0003 * sin(3 * M))
			L = (M + radians(C) + pi + radians(102.9372)) % (2 * pi)
			Jt = (0.5 + J + (0.0053 * sin(M)) - (0.0069 * sin(2 * L))) * 86400 + 946684800
			delta = asin(sin(L) * sin(radians(23.44)))
			omega = 86400 * acos((-0.0144857 - sin(lat) * sin(delta)) / (cos(lat) * cos(delta)))
			Jset = round(Jt + omega / (2 * pi))
			Jrise = round(Jt - omega / (2 * pi))
			f.write(str(Jrise) + "," + str(Jset) + "\n")
			n += 1

def downloadbin():
	"""Downloads and verifies the prebuilt firmware binary"""
	from gnupg import GPG
	from hashlib import sha512
	binname = "leyden.bin"
	signame = binname + ".sig"
	hashname = "sha512sums.asc"

	verb("Downloading files...")
	for n in [binname, signame, hashname]:
		with open(n, 'wb') as f:
			f.write(get(REPO + "raw/master/firmware/" + n).content)

	verb("Verifying files are correct...")
	with open(hashname, 'r') as hashfile, open(signame, 'r') as sigfile:
		gpg = GPG(gnupghome='')
		assert gpg.recv_keys("https://pgp.mit.edu/", MYPGP), \
			"could not import the necessary PGP key to verify the binary!"
		assert gpg.verify_file(hashfile), \
			"hash file signature cannot be verified!"
		assert gpg.verify_file(sigfile, binname), \
			"firmware binary file signature cannot be verified!"
		with open(binname, 'rb') as firmware:
			hasher = sha512()
			buf = firmware.read()
			hasher.update(buf)
			downloadhash = hasher.hexdigest()
		for line in hashfile.read():
			if downloadhash in line:
				return 0
		print("error: firmware binary has an incorrect hash! Please try again.")
		return 1

def flashbin():
	"""Flashes firmware and daytime list to the controller"""

def synctime():
	"""Synchronise clock on the controller"""

def getcurrentversion():
	"""Get the current version of the firmware on the connected controller"""
	return 1

def getlatestversion():
	"""Get the latest version of the firmware available"""
	return 1

def updatefirmware():
	"""Check if firmware needs updating, and flash the latest version if necessary"""
	synctime()
	if getcurrentversion() < getlatestversion():
		gensuntimes()
		downloadbin()
		flashbin()
	else:
		print("Your charge controller's firmware is up to date!")

def getdata():
	"""Get data log of power statistics from the controller"""

def monitordata():
	"""Show live statistics from the controller"""

def configure():
	"""Interactive configuration of the controller"""

if __name__ == "__main__":
	from argparse import ArgumentParser, RawDescriptionHelpFormatter
	P = ArgumentParser(description=__doc__,
	                   epilog=BUGS,
	                   formatter_class=RawDescriptionHelpFormatter)
	G = P.add_subparsers(title="commands",
	                     description="Use one of the following subcommands")
	P.add_argument("-v", "--verbose",
	               help="verbose output",
	               action="store_true")
	logs = G.add_parser("logs", help="get data logs from the controller")
	logs.set_defaults(func=getdata)
	config = G.add_parser("config", help="configure the controller")
	config.set_defaults(func=configure)
	monitor = G.add_parser("monitor", help="monitor data from the controller")
	monitor.set_defaults(func=monitordata)
	update = G.add_parser("update", help="update the controller firmware")
	update.set_defaults(func=updatefirmware)
	res = P.parse_args()
	VERB = res.verbose
	try:
		res.func()
	except AttributeError:
		P.print_usage()
